"""
默认模版
@author: 任磊
@software: Pycharm
@time: 2024/9/13 9:08
"""

"""
python实现自动获取小说内容
目标小说网站：https://www.readnovel.com/
目标小说网站：https://www.qidian.com/
目标小说网站：https://search.zongheng.com/s?keyword=1
简要日志：
基础：于2024-9-13完成
多进程：于2024-9-21完成
GUI：于2024-9-22完成
额外：于2024-9-24完成
补充：代码 330 处，可以酌情考虑添加

=========================================================== 该项目暂时中断 ====================================================

等待添加：
       1，多网站小说数据爬取功能:需要更据目标网站的结构进行分析，获取目标数据
       2，小说下载进度显示功能：添加进度条
       3，数据入库进度显示功能
       4，下载速度随机调整，防止被网站拉黑
       5，细节调整
       
"""

import sys
from bs4 import BeautifulSoup
import requests
import re
import os
import multiprocessing
from multiprocessing import Lock, Manager
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import *
from random import choice, randint
from PyQt5.QtGui import QIcon, QMovie, QPixmap, QImage
from PyQt5.QtWidgets import QApplication, QMainWindow, QAction, QTableWidgetItem, QAbstractItemView, QMessageBox, QWidget
from threading import Thread, Event
import jieba
import pandas as pd
from stylecloud import gen_stylecloud
from wordcloud import WordCloud
from PyQt5.QtWidgets import QPushButton, QVBoxLayout, QLabel, QDialog, QHBoxLayout
import time
import urllib.parse
import datetime
from PyQt5.QtCore import Qt
from PIL import Image, ImageQt
import pymysql
from dbutils.pooled_db import PooledDB


class Main_program(QWidget):
    version = "1.00"

    def __init__(self, form):
        super().__init__()
        """
        包含了实现该功能所需要的变量
        """
        self.url = "https://www.readnovel.com/"
        self.max_num = 0  # 限制数量
        self.counter = 0  # 下载数量计数
        self.next_url = ""  # 翻页的下一个网址入口
        self.base_path = r"C:\Users\LENOVO\Desktop"   # 基础保存路径
        self.path = r"H:\python_project\pythonProject1\python_project_1\1_get_text.txt"  # 自定义下载路径
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "zh-CN,zh;q=0.9",
            "Connection": "keep-alive",
            "Cookie": "_csrfToken=R7BrecwiMUmFLuKc8knJXVbbupJIIzASRV13u2Hd; newstatisticUUID=1726194509_926477367; fuid=66e3a34d7a92e; qdrs=0%7C3%7C0%7C0%7C2; qdgd=1; Hm_lvt_e21c7210dbc4db19e2bcc4a78428e778=1727004347,1727076103,1727232915,1727398401; HMACCOUNT=708B3D00B26D848E; traffic_utm_referer=https%3A//www.readnovel.com/; Hm_lpvt_e21c7210dbc4db19e2bcc4a78428e778=1727400373",
        }
        # 测试连接所用变量
        self.novel_name_list = []  # 小说名列表
        self.novel_author_list = []  # 小说作家列表
        # 目标小说所需变量
        self.novel_num = 0  # 目标小说序号
        self.novel_chapter_num = 1  # 目标小说下载章数
        self.process_num = 3  # 进程数量
        self.novel_url_list = []  # 小说对应链接
        self.chapter_url_list = []  # 小说的每章对应链接部分
        self.chapter_data_dict = {}  # 小说数据字典  未排序
        self.chapter_data_list = []  # 小说数据列表  已排序
        self.read_chapter_num = 0  # 当前预览的小说章节记录
        # 窗口参数
        self.form = form
        self.window = Window_program()
        self.start()
        # 全局参考标志
        self.novel_search_flag = False  # 用于判断是否已搜索小说
        self.novel_ready_flag = False  # 用于判断小说是否已选定
        self.t_event = Event()  # 多线程信号
        self.img = None  # 生成的词云图片所需变量
        self.qt_thread_work = None  # qt多线程：完成小说数据下载任务
        self.qt_thread_work2 = None  # qt多线程：完成词云生成任务
        self.load_flag = False  # 是否存入数据的标志
        self.work_flag = True  # 用于在执行任务时，禁止额外的操作

    def start(self) -> None:
        """
        启动程序的方法
        启动程序窗口，并将窗口内界面组件与响应的响应函数连接
        :return: None
        """
        self.window.setupUi(self.form)
        if self.window_init():
            self.window.statusbar.showMessage("成功启动", 5000)  # 设置状态，并在 5 秒后消失
        else:
            self.window.statusbar.showMessage("出现异常！", 5000)  # 设置状态，并在 5 秒后消失

    def window_init(self) -> bool:
        """
        用于进行系统界面初始化的方法
        返回系统初始化的结果
        :return: True/False
        """
        flag = True
        try:
            # 将界面的按钮与相应的方法连接
            self.window.pushButton.clicked.connect(self.button_search_function)  # 搜索按钮
            self.window.pushButton_2.clicked.connect(self.button_setting_ready_function)  # 配置确定按钮
            self.window.pushButton_3.clicked.connect(self.button_setting_reset_function)  # 配置重置按钮
            self.window.pushButton_4.clicked.connect(self.button_lastChapter_function)  # 上一章按钮
            self.window.pushButton_5.clicked.connect(self.button_nextChapter_function)  # 下一章按钮
            self.window.pushButton_6.clicked.connect(self.button_toChapter_function)  # 跳转按钮
            self.window.commandLinkButton.clicked.connect(self.button_download_function)  # 下载按钮
            self.window.pushButton_g.clicked.connect(self.button_wordCloud_function)  # 生成词云按钮
            self.window.commandLinkButton_s.clicked.connect(self.button_save_function)  # 保存按钮
        except AttributeError:
            flag = False
        finally:
            return flag

    def button_search_function(self) -> None:
        """
        用于实现搜索的方法
        :return:None
        """
        self.window.pushButton.clicked.disconnect()
        novel = self.window.lineEdit.text()
        self.window.textEdit_2.clear()
        if novel == "":
            self.window.textEdit_2.setText("小说名不可为空！")
        elif self.work_flag is False:
            self.window.textEdit_2.setText("当前不可操作！")
        else:
            # 重置已有数据
            self.novel_name_list.clear()
            self.novel_author_list.clear()
            self.novel_url_list.clear()
            self.chapter_url_list.clear()
            self.chapter_data_dict.clear()
            self.chapter_data_list.clear()
            self.window.tableWidget.setRowCount(0)
            self.img = None
            self.load_flag = False
            # 开始搜索
            self.window.textEdit_2.setText("已开始搜索")
            if self.test_name_2(novel):
                # 在界面中填入数据
                novel_num = len(self.novel_name_list)
                self.window.tableWidget.setRowCount(novel_num)
                for num, data in enumerate(self.novel_name_list):
                    self.window.tableWidget.setItem(num, 0, QTableWidgetItem("".join(data)))
                    self.window.tableWidget.setItem(num, 1, QTableWidgetItem("".join(self.novel_author_list[num])))
                self.novel_search_flag = True
            else:
                pass
            self.window.statusbar.showMessage("完毕!", 2000)
        self.window.pushButton.clicked.connect(self.button_search_function)

    def button_setting_ready_function(self) -> None:
        """
        用于确定按钮的相应函数
        :return: None
        """
        self.window.pushButton_2.clicked.disconnect()
        self.window.textEdit_2.clear()
        if self.work_flag is False:
            self.window.textEdit_2.setText("当前不可操作！")
        elif self.novel_search_flag:
            novel_num = self.window.lineEdit_2.text()  # 小说序号
            chapter_num = self.window.lineEdit_3.text()  # 小说章节数
            if novel_num != "":
                if novel_num.isdigit():
                    novel_num = int(novel_num) - 1
                    if 0 <= novel_num <= len(self.novel_name_list)-1:
                        if chapter_num != "":
                            if chapter_num.isdigit():
                                self.novel_num = novel_num
                                self.novel_chapter_num = chapter_num
                                self.process_num = self.window.comboBox.currentText()
                                self.novel_ready_flag = True
                                self.window.textEdit_2.setText("配置已就绪\n可以开始下载")
                            else:
                                self.window.textEdit_2.setText("请输入数字！")
                        else:
                            self.window.textEdit_2.setText("章节数不可为空！")
                    else:
                        self.window.textEdit_2.setText("不存在的序号！")
                else:
                    self.window.textEdit_2.setText("请输入数字！")
            else:
                self.window.textEdit_2.setText("序号不可为空！")
        else:
            self.window.textEdit_2.setText("请先搜索小说！")
        self.window.pushButton_2.clicked.connect(self.button_setting_ready_function)

    def button_setting_reset_function(self) -> None:
        """
        重置按钮的相应方法
        :return: None
        """
        self.window.pushButton_3.clicked.disconnect()
        self.window.lineEdit_2.setText("")
        self.window.lineEdit_3.setText("")
        self.window.comboBox.setCurrentText("1")
        self.window.textEdit_2.clear()
        self.novel_ready_flag = False
        self.window.textEdit_2.setText("配置已重置！")
        self.window.pushButton_3.clicked.connect(self.button_setting_reset_function)

    def button_download_function(self) -> None:
        """
        下载按钮的相应方法
        :return: None
        """
        self.window.commandLinkButton.clicked.disconnect()
        self.window.textEdit_2.clear()
        if self.work_flag is False:
            self.window.textEdit_2.setText("当前不可操作！")
        elif self.novel_ready_flag:
            self.window.statusbar.showMessage("下载中...", 5000)
            # th = threading.Thread(target=self.process_start, args=(self.novel_chapter_num, ))
            # th.start()
            # th2 = threading.Thread(target=self.wait_show_data)
            # th2.start()
            # 开启QT线程
            self.qt_thread_work = QThread_program(self, self.novel_num)
            self.qt_thread_work.start()
            self.qt_thread_work.finished.connect(self.wait_show_data)
        else:
            self.window.textEdit_2.setText("请先确定配置！")
        self.window.commandLinkButton.clicked.connect(self.button_download_function)

    def button_lastChapter_function(self) -> None:
        """
        上一章按钮的响应方法
        :return: None
        """
        self.window.pushButton_4.clicked.disconnect()
        self.window.textEdit_2.clear()
        if len(self.chapter_data_list) != 0:
            if self.read_chapter_num == 0:
                self.window.textEdit_2.setText("当前已经是第一章了！")
            else:
                self.read_chapter_num -= 1
                self.window.textEdit.clear()
                self.window.textEdit.setText(str(self.chapter_data_list[self.read_chapter_num]))
                self.window.textEdit_2.setText("已切入上一章")
        else:
            self.window.textEdit_2.setText("当前没有内容！")
        self.window.pushButton_4.clicked.connect(self.button_lastChapter_function)

    def button_nextChapter_function(self) -> None:
        """
        下一章按钮的响应方法
        :return: None
        """
        self.window.pushButton_5.clicked.disconnect()
        self.window.textEdit_2.clear()
        if len(self.chapter_data_list) != 0:
            if self.read_chapter_num == len(self.chapter_data_list) - 1:
                self.window.textEdit_2.setText("当前已经是最后一章了！")
            else:
                self.read_chapter_num += 1
                self.window.textEdit.clear()
                self.window.textEdit.setText(str(self.chapter_data_list[self.read_chapter_num]))
                self.window.textEdit_2.setText("已切入下一章")
        else:
            self.window.textEdit_2.setText("当前没有内容！")
        self.window.pushButton_5.clicked.connect(self.button_nextChapter_function)

    def button_toChapter_function(self) -> None:
        """
        跳转到指定章数按钮的响应方法
        :return: None
        """
        self.window.pushButton_6.clicked.disconnect()
        self.window.textEdit_2.clear()
        target_num = self.window.lineEdit_5.text()
        if len(self.chapter_data_list) != 0:
            if target_num != "":
                if target_num.isdigit():
                    target_num = int(target_num) - 1
                    if 0 <= target_num <= len(self.chapter_data_list) - 1:
                        self.read_chapter_num = target_num
                        self.window.textEdit.clear()
                        self.window.textEdit.setText(self.chapter_data_list[target_num])
                        self.window.textEdit_2.setText("已切换至第{}章".format(target_num+1))
                    else:
                        self.window.textEdit_2.setText("不存在该章数！")
                else:
                    self.window.textEdit_2.setText("请输入数字！")
            else:
                self.window.textEdit_2.setText("章数不能为空！")
        else:
            self.window.textEdit_2.setText("当前没有内容！")
        self.window.pushButton_6.clicked.connect(self.button_toChapter_function)

    def button_wordCloud_function(self) -> None:
        """
        用于词云生成按钮的响应函数
        :return: None
        """
        self.window.pushButton_g.clicked.disconnect()
        self.window.textEdit_2.clear()
        if len(self.chapter_data_list) != 0:
            if self.work_flag:
                data = self.chapter_data_list[self.read_chapter_num]
                self.qt_thread_work2 = Wordcloud_program("{}第{}章词云".format(self.novel_name_list[int(self.novel_num)], self.read_chapter_num+1), data)  # 传入当前章节的数据
                self.qt_thread_work2.finished.connect(self.wait_wordcloud_function)
                self.qt_thread_work2.start()
                self.work_flag = False  # 修改全局工作状态标志
            else:
                self.window.textEdit_2.setText("当前不能操作！")
        else:
            self.window.textEdit_2.setText("当前没有数据！")
        self.window.pushButton_g.clicked.connect(self.button_wordCloud_function)

    def button_save_function(self) -> None:
        """
        保存按钮的响应函数
        :return: None
        """
        self.window.commandLinkButton_s.clicked.disconnect()
        self.window.textEdit_2.clear()
        if len(self.chapter_data_list) != 0:
            if self.work_flag:
                if self.load_flag is False:
                    self.load_flag = True
                    self.window.textEdit_2.setText("保存操作已执行\n")
                    pass  # todo 调用保存数据的方法/或存入数据库？
                    word_num = 0
                    for i in range(len(self.chapter_data_list)):  # 对即将存入的数据进行计数
                        word_num += len(self.chapter_data_list[i])
                    self.window.textEdit_2.setText("将存入{}个字符数据！".format(word_num))
                else:
                    self.window.textEdit_2.setText("数据已经保存过了")
                    qb = QMessageBox(QMessageBox.Warning, "提示", '数据已经保存过了！', QMessageBox.Ok)
                    qb.exec_()
            else:
                self.window.textEdit_2.setText("当前不能操作！")
        else:
            self.window.textEdit_2.setText("当前没有数据！")
        self.window.commandLinkButton_s.clicked.connect(self.button_save_function)

    def wait_save_function(self, result_dict: dict) -> None:
        """
        在保存数据至数据库后，页面的响应方法
        :param result_dict: 线程返回的信号字典
        :return: None
        """
        self.work_flag = True
        # 清除提示框内数据
        self.window.textEdit_2.clear()
        flag = True
        error_data = ''  # 记录错误信息
        try:
            flag1 = result_dict.get("flag1")
            error_list = result_dict.get("flag2")
            # 判断小说基本信息入库情况
            if not flag1:
                error_data += "小说基本信息入库失败！\n"
                flag = False
            # 判断小说各章节入库情况
            for i, data in enumerate(error_list):
                error_data += f"第{i + 1}章入库失败！\n"
                flag = False
        except KeyError as e:
            self.window.textEdit_2.setText(f"字典键值对丢失，请检查！\n{e}")
            flag = False
        finally:
            if flag:
                self.window.textEdit_2.setText("数据库保存成功！")
            else:
                self.window.textEdit_2.setText("数据库保存失败！\n{}".format(error_data))

    def wait_wordcloud_function(self, img_dict: dict) -> None:
        """
        生成词云后的响应函数
        接收生成词云后的图片地址
        :param img_dict: 含有图片数据的字典
        :return: None
        """
        # 修改全局工作状态标志
        self.work_flag = True

        # 获取指点中的值，并添加异常处理
        flag = None
        img_name = None
        img = None
        error = None
        try:
            flag = img_dict.get("flag")
            img_name = str(img_dict.get("img_name"))
            error = img_dict.get("error")
            img = img_dict.get("img")
        except KeyError as e:
            self.window.textEdit_2.setText("字典中缺少必要的键值对，请检查！{}".format(e))

        # 清空文本编辑框
        self.window.textEdit_2.clear()
        # 处理成功情况
        if flag is True:
            self.window.textEdit_2.setText("本章词云图已生成！")
            # 使用os.path.join()拼接路径
            last_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # 获得文件的上一个文件夹
            img_path = os.path.join(last_path, img_name)
            # 显示图像对话框
            dialog = ImageDialog_program(img_path, img_name, img)
            dialog.exec_()
        else:
            # 处理失败情况
            self.window.textEdit_2.setText("本章词云图生成失败！\n错误：{}".format(error))

    def test_name_1(self, name):
        """
        测试目标小说是否存在的方法
        同时获得相似小说的名字和作者
        :param name: 小说名
        :return: True or False
        """
        success_flag = True
        self.window.textEdit_2.clear()
        try:
            self.work_flag = False
            encoded_name = urllib.parse.quote(name)
            response = requests.get(f"https://www.readnovel.com/so/{encoded_name}", headers=self.headers, timeout=10)
            if response.status_code == 200:
                soup = BeautifulSoup(response.text, "html.parser")
                book_container = soup.find("div", attrs={"class": "book-img-text"})
                if book_container is not None:
                    book_info = book_container.findAll("div", attrs={"class": "book-mid-info"})
                    soup = BeautifulSoup(str(book_info), "html.parser")
                    book_links = soup.findAll("a", attrs={"target": "_blank"})
                    author_info = soup.findAll("p", attrs={"class": "author"})
                    soup = BeautifulSoup(str(author_info), "html.parser")
                    author_links = soup.findAll("a", attrs={"class": "name default"})

                    for index in range(0, len(book_links), 3):
                        novel_title = re.findall(r'[\u4e00-\u9fff]+', str(book_links[index]))  # 匹配小说名
                        novel_url = re.findall(r'href=".*target', str(book_links[index]))  # 匹配小说url
                        novel_url = str(novel_url[0][5:]).strip('" target')
                        self.novel_url_list.append(novel_url)  # 获得小说对应url
                        self.novel_name_list.append("".join(novel_title))  # 获得小说名

                    for index in range(len(author_links)):
                        self.novel_author_list.append("".join(soup.text))  # 获得作者名
                    print(self.novel_author_list)
                else:
                    success_flag = False
        except requests.RequestException as e:
            self.window.textEdit_2.setText(f"请求错误，请检查网络连接！\n{e}")
            success_flag = False
        except Exception as e:
            self.window.textEdit_2.setText(f"未知错误，请检查日志！\n{e}")
            success_flag = False
        finally:
            self.work_flag = True
            return success_flag

    def test_name_2(self, name):
        """
        测试目标小说是否存在的方法
        同时获得相似小说的名字和作者
        :param name: 小说名
        :return: True or False
        """
        success_flag = True
        self.window.textEdit_2.clear()
        try:
            self.work_flag = False
            encoded_name = urllib.parse.quote(name)
            print("就绪")
            response = requests.get(f"https://www.qidian.com/so/{encoded_name}.html", headers=self.headers, timeout=10)
            print(response.status_code)
            if response.status_code == 200:
                soup = BeautifulSoup(response.text, "html.parser")
                book_container = soup.find("div", attrs={"class": "book-img-text"})
                if book_container is not None:
                    book_info = book_container.findAll("li")
                    print(book_info[0])
                    soup = BeautifulSoup(str(book_info), "html.parser")
                    book_links = soup.findAll("a", attrs={"data-eid": "qd_S04"})  # 获取书本链接列表
                    author_info = soup.findAll("p", attrs={"class": "author"})  # 获取作者名列表
                    soup = BeautifulSoup(str(author_info), "html.parser")
                    author_links = soup.findAll("a", attrs={"class": "name"})

                    print(book_links[0:3])
                    print("\n")
                    print(author_links[0:3])
                    # for index in range(0, len(book_links), 3):
                    #     novel_title = re.findall(r'[\u4e00-\u9fff]+', str(book_links[index]))  # 匹配小说名
                    #     novel_url = re.findall(r'href=".*target', str(book_links[index]))  # 匹配小说url
                    #     novel_url = str(novel_url[0][5:]).strip('" target')
                    #     self.novel_url_list.append(novel_url)  # 获得小说对应url
                    #     self.novel_name_list.append("".join(novel_title))  # 获得小说名
                    #
                    # for index in range(len(author_links)):
                    #     self.novel_author_list.append("".join(soup.text))  # 获得作者名
                    print(self.novel_author_list)
                else:
                    success_flag = False
        except requests.RequestException as e:
            self.window.textEdit_2.setText(f"请求错误，请检查网络连接！\n{e}")
            success_flag = False
        except Exception as e:
            self.window.textEdit_2.setText(f"未知错误，请检查日志！\n{e}")
            success_flag = False
        finally:
            self.work_flag = True
            return success_flag

    def run(self):  # 旧版
        """
        面对用户直接服务的实现方法
        :return: None
        """
        print("欢迎使用小说自动爬取器")
        target_name = input("请输入目标小说名：")
        if self.test_name_1(target_name) is True:
            print("查找到以下相似小说名：")
            for num, data in enumerate(self.novel_name_list):
                print("{}  小说名：{}  作者：{}".format(num, data, self.novel_author_list[num]))
            choose = input("请输入目标小说序号：")
            while True:
                if ("0" <= choose <= str(len(self.novel_name_list))) and choose.isdigit():
                    break
                else:
                    print("不存在的输入，请重试！")
                    choose = input("请输入目标序号：")
            num = input("请输入下载章数：")
            while True:
                if num.isdigit():
                    break
                else:
                    print("不存在的输入，请重试！")
                    num = input("请输入下载章数：")
            self.max_num = int(num)
            # flag = self.process_start(choose)
            # if flag:
            #     # 测试
            #     for data in self.chapter_data_dict.values():
            #         print(data)  # 打印数据
            # else:
            #     print("失败")
            self.download_init(choose)
        else:
            print("\033[91m 未能查找到目标小说名! \033[0m")

    def download_init(self, choose):  # 旧版
        """
        实现具体数据的下载的方法
        :return: None
        """
        flag = True
        content = ""
        soup = ''
        try:
            response = requests.get(self.url + str(self.novel_url_list[int(choose)]), self.headers)
            if response.ok:
                soup = BeautifulSoup(response.text, "html.parser")
                content = soup.findAll("div", attrs={"class": "volume-wrap"})  # 查找到目标位置
                soup = BeautifulSoup(str(content), "html.parser")
                content = soup.findAll("div", attrs={"class": "volume"})
                soup = BeautifulSoup(str(content), "html.parser")
                content = soup.findAll("li")
                for i in range(self.max_num):  # 收纳指定小说数量
                    data = content[i]
                    next_url = re.findall(r'href=".*target', str(data))  # 匹配小说url
                    next_url = str(next_url[0][5:]).strip('" target')
                    self.chapter_url_list.append(next_url)
            else:
                flag = False
        except RuntimeError:
            flag = False
        finally:
            if flag is True:
                if os.path.exists(self.path) is True:
                    for num, next_url in enumerate(self.chapter_url_list):  # 将列表中所有的数据进行下载
                        if self.download(next_url) is True:
                            print("第 {} 章下载完毕".format(num + 1))
                        else:
                            print("\033[91m 第 {} 章下载失败！ \033[0m".format(num + 1))
                else:
                    print("\033[91m 无法找到对应存储地址！ \033[0m")
            else:
                print("\033[91m 获取失败！ \033[0m")

    def download(self, next_url):  # 旧版
        """
        下载单个数据的方法
        :param next_url:含有数据下载地址部分连接
        :return: True/False
        """
        url = self.url + str(next_url)
        flag = False
        try:
            response = requests.get(url, self.headers)  # 获得图片网址
            if response.status_code == 200:
                # response.encoding = 'utf-8'
                soup = BeautifulSoup(response.text, "html.parser")
                # 获得本章标题
                title = soup.find("div", attrs={"class": "text-head"})
                soup1 = BeautifulSoup(str(title), "html.parser")
                title = soup1.find("h1")
                title = title.text  # 章节名
                # 获得本章内容
                contents = soup.find("div", attrs={"class": "ywskythunderfont"})
                soup = BeautifulSoup(str(contents), "html.parser")
                contents = soup.text  # 章节内容
                with open(self.path, 'a') as f:
                    f.write(str(title) + "\n")
                    f.write(str(contents) + "\n")
                flag = True
            else:
                pass
        except RuntimeError:
            flag = False
        finally:
            return flag

    def process_start(self, choose) -> None:
        """
        启用多进程的方法
        :return:None
        """
        flag = True
        try:
            self.work_flag = False
            response = requests.get(self.url + str(self.novel_url_list[int(choose)]), self.headers)
            if response.ok:
                soup = BeautifulSoup(response.text, "html.parser")
                content = soup.findAll("div", attrs={"class": "volume-wrap"})  # 查找到目标位置
                soup = BeautifulSoup(str(content), "html.parser")
                content = soup.findAll("div", attrs={"class": "volume"})
                soup = BeautifulSoup(str(content), "html.parser")
                content = soup.findAll("li")
                for i in range(int(self.novel_chapter_num)):  # 收纳指定小说数量
                    data = content[i]
                    next_url = re.findall(r'href=".*target', str(data))  # 匹配小说url
                    next_url = str(next_url[0][5:]).strip('" target')
                    self.chapter_url_list.append(next_url)
            else:
                flag = False
        except RuntimeError:
            flag = False
        finally:
            if flag is True:
                # print("就绪！")
                # process = Process_program(self.process_num)
                # print("进程池 就绪！")
                # with Manager() as manager:
                #     print("共享 就绪！")
                #     chapter_data = manager.dict()
                #     for chapter_num, chapter_url in enumerate(self.chapter_url_list):
                #         print("{} 已发出！".format(chapter_num))
                #         chapter_url = self.url + chapter_url
                #         print(chapter_url)
                #         head = self.headers
                #         process.pool.apply_async(func=Main_program.process_work, args=(head, chapter_num, chapter_url, chapter_data))
                #     self.chapter_data_dict.update(chapter_data)
                #     print("获得：{}".format(chapter_data))
                # process.pool.close()
                # process.pool.join()
                pool = multiprocessing.Pool(int(self.process_num))
                with Manager() as manager:
                    chapter_data = manager.dict()
                    for chapter_num, chapter_url in enumerate(self.chapter_url_list):
                        chapter_url = self.url + chapter_url
                        head = self.headers
                        # process = Process(target=Main_program.process_work,
                        #                   args=(head, chapter_num, chapter_url, chapter_data))
                        # process.start()
                        pool.apply_async(func=Main_program.process_work, args=(head, chapter_num, chapter_url, chapter_data))
                    pool.close()
                    pool.join()
                    self.chapter_data_dict.update(chapter_data)
            self.work_flag = True

    @staticmethod
    def process_work(head, chapter_num, chapter_url, chapter_data):
        """
        获得小说每一章内容的方法
        :return:None
        """
        global p_lock
        url = chapter_url
        flag = False
        title = ""
        contents = ""
        try:
            response = requests.get(url, head)  # 获得
            if response.status_code == 200:
                # response.encoding = 'utf-8'
                soup = BeautifulSoup(response.text, "html.parser")
                # 获得本章标题
                title = soup.find("div", attrs={"class": "text-head"})
                soup1 = BeautifulSoup(str(title), "html.parser")
                title = soup1.find("h1")
                title = title.text  # 章节名
                # 获得本章内容
                contents = soup.find("div", attrs={"class": "ywskythunderfont"})
                soup = BeautifulSoup(str(contents), "html.parser")
                contents = soup.text  # 章节内容
                flag = True
            else:
                pass
        except RuntimeError:
            flag = False
        finally:
            p_lock.acquire()
            if flag:
                chapter_data.update({"{}".format(chapter_num): str(title + "\n" + contents)})
            else:
                chapter_data.update({"{}".format(chapter_num): "FALSE_TO_DOWNLOAD"})
            p_lock.release()

    def wait_show_data(self):
        """"
        等待下载完成后在预览框显示小说数据时调用的方法
        """
        # 判断下载结果
        if len(self.chapter_data_dict) != 0:
            self.window.textEdit_2.setText("下载完成")
            # 排序
            self.chapter_data_list = ["" for _ in range(len(self.chapter_data_dict))]
            for key, value in self.chapter_data_dict.items():
                self.chapter_data_list[int(key)] = value
            self.chapter_data_dict.clear()  # 清空
            self.window.textEdit.setText(self.chapter_data_list[0])  # 默认展示第一章内容
            self.read_chapter_num = 0
        else:
            self.window.textEdit_2.setText("下载失败！")


class QThread_program(QThread):
    """
    用于在多线程中，完成小说数据的下载任务
    """
    trigger = pyqtSignal(str)
    finished = pyqtSignal()

    def __init__(self, body: Main_program, choose):
        super().__init__()
        self.body = body
        self.choose = choose

    def run(self):
        self.body.process_start(self.choose)
        self.finished.emit()  # 传递已下载完成的信号


class Wordcloud_program(QThread):
    """
    用于实现在多线程中，完成目标数据的词云生成
    """
    trigger = pyqtSignal(str)
    finished = pyqtSignal(dict)
    stop_words = {'你', '我', '的', '了', '在', '吧', '相信', '是', '也', '都', '不', '吗', '就', '我们', '还', '大家', '你们', '就是', '以后'}

    def __init__(self, img_name: str, data: str):
        super().__init__()
        self.data = str(data)
        self.img_name = img_name

    def run(self):
        # 取出数据中的空格
        end_data = ""
        for item in self.data.split():
            end_data += item
        self.data = ""  # 置空
        # 去除停用词
        for word in end_data:
            if word not in self.stop_words:
                self.data += word
        # 处理词云
        # wordlist = jieba.cut_for_search(''.join(self.data))
        jieba.setLogLevel(jieba.logging.INFO)  # 使红色提示不显示
        wordlist = jieba.cut(''.join(self.data))
        text = ''.join(wordlist)
        # 生成图片
        self.img_name = self.img_name + ".png"
        flag = True
        error = None
        img = None
        try:
            wc = WordCloud(font_path="simhei.ttf", width=800, height=600, mode="RGBA", background_color=None).generate(text)
            img = wc.to_image()
        except ImportError as e:
            error = str(e)
            flag = False
        except ValueError as e:
            error = str(e)
            flag = False
        except AttributeError as e:
            error = str(e)
            flag = False
        except RuntimeError as e:
            error = str(e)
            flag = False
        except Exception as e:
            error = str(e)
            flag = False
        finally:
            data_dict = {
                "img_name": self.img_name,
                "flag": flag,
                "error": error,
                "img": img
            }
        # 保存图片
        self.finished.emit(data_dict)


class ImageDialog_program(QDialog):
    """
    用于独立显示生成词云的窗口显示
    """

    def __init__(self, image_path, img_name: str, img: Image):
        """
        实现词云的显示方法
        :param image_path:图片地址
        :param img_name:图片名
        :param img:图片
        """
        super().__init__()
        self.image_path = "./" + image_path
        self.img = img
        self.img_name = img_name
        self.flag = True
        self.load_flag = False
        layout = QVBoxLayout()
        image_label = QLabel()  # 设置标签
        self.button_s = QPushButton("保存")  # 设置按钮
        try:
            # 设置词云的图片的标签
            self.setWindowTitle('{}'.format(img_name))
            qimage = QImage(img.tobytes(), img.width, img.height, QImage.Format_ARGB32)
            pixmap = QPixmap.fromImage(qimage)
        except Exception as e:  # 捕获错误
            # print(e)  #  这里似乎一直在报 name 'ImageQt' is not defined 的错误 (已修复)
            self.flag = False
            pixmap = QPixmap("./NoneThatImg.png")
            self.setWindowTitle(f"未找到目标图片！{e}")
        finally:
            self.button_s.clicked.connect(self.button_save_function)
        image_label.setPixmap(pixmap)
        image_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(image_label)
        layout.addWidget(self.button_s)
        self.setLayout(layout)

    def button_save_function(self) -> None:
        """
        实现保存按钮的响应方法
        :return: None
        """
        self.button_s.clicked.disconnect()
        if self.flag:  # 判断图片是否以获取
            if self.load_flag is False:  # 判断图片是否已下载
                self.load_flag = True
                self.img.save(self.image_path)
                qb = QMessageBox(QMessageBox.Warning, "提示", '图片已保存！', QMessageBox.Ok)
                qb.exec_()
            else:
                qb = QMessageBox(QMessageBox.Warning, "提示", '图片已经保存过了！', QMessageBox.Ok)
                qb.exec_()
        else:
            qb = QMessageBox(QMessageBox.Warning, "提示", '图片保存失败！', QMessageBox.Ok)
            qb.exec_()
        self.button_s.clicked.connect(self.button_save_function)


class Window_program(QWidget):
    """
    实现主窗口的类
    """

    def __init__(self):
        super().__init__()
        pass

    def setupUi(self, MainWindow) -> None:
        """
        用于实现窗口的方法
        :param MainWindow:
        :return: None
        """
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(725, 500)
        MainWindow.setFixedSize(725, 500)  # 固定窗口大小
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        # 标签：搜索小说
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(20, 10, 100, 30))
        self.label.setObjectName("label")
        # 输入栏：搜索小说
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(130, 10, 160, 30))
        self.lineEdit.setText("")
        self.lineEdit.setObjectName("lineEdit")
        # 按钮：搜索小说
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(300, 10, 80, 30))
        self.pushButton.setObjectName("pushButton")
        # 列表栏：小说数据列表
        self.tableWidget = QtWidgets.QTableWidget(self.centralwidget)
        self.tableWidget.setGeometry(QtCore.QRect(20, 50, 360, 260))
        self.tableWidget.setObjectName("tableWidget")
        self.tableWidget.setColumnCount(2)
        self.tableWidget.setRowCount(0)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(1, item)
        # 文本框：小说内容
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(400, 50, 300, 260))
        self.textEdit.setObjectName("textEdit")
        # 标签：小说内容
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(490, 10, 100, 30))
        self.label_2.setObjectName("label_2")
        # 标签：小说序号
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(20, 320, 40, 30))
        self.label_3.setObjectName("label_3")
        # 标签：章数
        self.label_4 = QtWidgets.QLabel(self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(20, 350, 40, 30))
        self.label_4.setObjectName("label_4")
        # 输入栏：小说序号
        self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_2.setGeometry(QtCore.QRect(90, 320, 100, 25))
        self.lineEdit_2.setObjectName("lineEdit_2")
        # 输入栏：章数
        self.lineEdit_3 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_3.setGeometry(QtCore.QRect(90, 350, 100, 25))
        self.lineEdit_3.setObjectName("lineEdit_3")
        # 标签：进程数
        self.label_5 = QtWidgets.QLabel(self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(20, 380, 100, 30))
        self.label_5.setObjectName("label_5")
        # 选择栏：进程数
        self.comboBox = QtWidgets.QComboBox(MainWindow)
        self.comboBox.setGeometry(QtCore.QRect(90, 380, 100, 25))
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        # 按钮：确定
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(20, 420, 75, 30))
        self.pushButton_2.setObjectName("pushButton_2")
        # 按钮：重置
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setGeometry(QtCore.QRect(110, 420, 75, 30))
        self.pushButton_3.setObjectName("pushButton_3")
        # 文本框：提示内容
        self.textEdit_2 = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit_2.setGeometry(QtCore.QRect(210, 320, 170, 80))
        self.textEdit_2.setObjectName("textEdit_2")
        # 按钮：下载
        self.commandLinkButton = QtWidgets.QCommandLinkButton(self.centralwidget)
        self.commandLinkButton.setGeometry(QtCore.QRect(210, 410, 140, 40))
        self.commandLinkButton.setObjectName("commandLinkButton")
        # 按钮：上一章
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setGeometry(QtCore.QRect(400, 320, 75, 30))
        self.pushButton_4.setObjectName("pushButton_4")
        # 按钮：下一章
        self.pushButton_5 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_5.setGeometry(QtCore.QRect(510, 320, 75, 30))
        self.pushButton_5.setObjectName("pushButton_5")
        # 标签：跳转
        self.label_6 = QtWidgets.QLabel(self.centralwidget)
        self.label_6.setGeometry(QtCore.QRect(400, 360, 100, 30))
        self.label_6.setObjectName("label_6")
        # 输入栏：跳转
        self.lineEdit_5 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_5.setGeometry(QtCore.QRect(510, 360, 100, 30))
        self.lineEdit_5.setObjectName("lineEdit_5")
        # 按钮：跳转
        self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_6.setGeometry(QtCore.QRect(620, 360, 75, 30))
        self.pushButton_6.setObjectName("pushButton_6")
        # 按钮：词云生成
        self.pushButton_g = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_g.setGeometry(QtCore.QRect(400, 400, 100, 30))
        self.pushButton_g.setObjectName("pushButton_g")
        # 按钮：保存数据
        self.commandLinkButton_s = QtWidgets.QCommandLinkButton(self.centralwidget)
        self.commandLinkButton_s.setGeometry(QtCore.QRect(550, 410, 140, 40))
        self.commandLinkButton_s.setObjectName("commandLinkButton_s")
        # ...
        MainWindow.setCentralWidget(self.centralwidget)
        # 状态栏
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        # 修改界面标题
        MainWindow.setWindowTitle("小说下载器 {}".format(Main_program.version))  # 标题

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "目标小说名："))
        self.pushButton.setText(_translate("MainWindow", "搜索"))
        self.label_2.setText(_translate("MainWindow", "内容预览"))
        self.label_3.setText(_translate("MainWindow", "序号："))
        self.label_4.setText(_translate("MainWindow", "章数："))
        self.lineEdit.setPlaceholderText(_translate("MainWindow", "输入小说名"))
        self.lineEdit_2.setPlaceholderText(_translate("MainWindow", "小说序号"))
        self.lineEdit_3.setPlaceholderText(_translate("MainWindow", "下载章数"))
        self.label_5.setText(_translate("MainWindow", "进程数："))
        # self.lineEdit_4.setPlaceholderText(_translate("MainWindow", "进程数<=3"))
        self.comboBox.setItemText(0, _translate("Form", "1"))
        self.comboBox.setItemText(1, _translate("Form", "2"))
        self.comboBox.setItemText(2, _translate("Form", "3"))
        self.pushButton_2.setText(_translate("MainWindow", "确定"))
        self.pushButton_3.setText(_translate("MainWindow", "重置"))
        self.commandLinkButton.setText(_translate("MainWindow", "开始下载"))
        self.pushButton_4.setText(_translate("MainWindow", "上一章"))
        self.pushButton_5.setText(_translate("MainWindow", "下一章"))
        self.label_6.setText(_translate("MainWindow", "目标章节数："))
        self.lineEdit_5.setPlaceholderText(_translate("MainWindow", "输入章节数"))
        self.pushButton_6.setText(_translate("MainWindow", "跳转"))
        item = self.tableWidget.horizontalHeaderItem(0)
        item.setText(_translate("MainWindow", "小说名"))
        item = self.tableWidget.horizontalHeaderItem(1)
        item.setText(_translate("MainWindow", "作者"))
        self.pushButton_g.setText(_translate("MainWindow", "本章词云"))
        self.commandLinkButton_s.setText(_translate("MainWindow", "保存数据"))


class Mysql_program(QThread):
    # 数据库信息
    DB_HOST = "localhost"  # 主机地址
    DB_PORT = 3306  # 端口
    DB_USER = "root"  # 用户名
    DB_PASSWORD = "3014007467"  # 密码
    DB_NAME = "project_all"  # 数据库名称
    DB_CHARSET = "utf8"  # 编码格式
    LOG_LEVEL = {
        "1": 'INFO',  # 默认
        "2": 'WARN',  # 警告
        "3": 'ERROR',  # 错误
        "4": 'FATAL',  # 严重
        "5": 'FATAL',  # 严重，程序无法继续运行
        "6": 'DEBUG'  # 调试
    }  # 日志等级，严重程度依次增加，最后一个除外

    # 连接池参数
    DB_MIN_CACHED = 1  # 初始化时开启的闲置连接数量
    DB_MAX_CACHED = 3  # 连接池中允许的闲置的最多连接数量
    DB_MAX_CONNECTIONS = 10  # 创建连接池的最大数量
    DB_MAX_SHARED = 5  # 共享连接数允许的最大数量
    DB_BLOCKING = True  # 当连接池达到最大数量时的行为，True表示阻塞等待
    DB_MAX_USAGE = 0  # 单个连接的最大允许复用次数，0表示无限制

    trigger = pyqtSignal(str)
    finished = pyqtSignal(dict)  # 线程完成标志

    def __init__(self, data_dict: dict):
        super().__init__()
        self.data_dict = data_dict
        """
        初始化并创建数据库连接池
        """
        self.pool = PooledDB(
            creator=pymysql,  # 使用pymysql作为数据库连接对象
            mincached=Mysql_program.DB_MIN_CACHED,  # 初始化时开启的闲置连接数量
            maxcached=Mysql_program.DB_MAX_CACHED,  # 连接池中允许的闲置的最多连接数量
            maxconnections=Mysql_program.DB_MAX_CONNECTIONS,  # 创建连接池的最大数量
            maxshared=Mysql_program.DB_MAX_SHARED,  # 共享连接数允许的最大数量
            blocking=Mysql_program.DB_BLOCKING,  # 当连接池达到最大数量时的行为
            maxusage=Mysql_program.DB_MAX_USAGE,  # 单个连接的最大允许复用次数
            host=Mysql_program.DB_HOST,
            port=Mysql_program.DB_PORT,
            user=Mysql_program.DB_USER,
            passwd=Mysql_program.DB_PASSWORD,
            db=Mysql_program.DB_NAME,
            charset=Mysql_program.DB_CHARSET
        )

    def run(self):
        """
        启动小说数据存放至数据库的方法
        :return: None
        """
        # 从连接池中获得数据的连接
        conn = self.pool.connection()
        cursor = conn.cursor()
        # 拆解并获得小说数据
        novel_dict = {
            'novel_title': self.data_dict.get('novel_title'),
            'novel_author': self.data_dict.get('novel_author'),
            'novel_summary': self.data_dict.get('novel_summary'),
            'novel_status': self.data_dict.get('novel_status')
        }
        # 调用小说基本信息存入的方法
        flag1 = Mysql_program.sql_novelBase_commit(self.pool, novel_dict)  # 记录小说存储结果
        # 获取小说id
        novel_id = cursor.execute("select novel_id from project1_1 where title = '{}' and author = '{}';".format(self.data_dict.get('novel_title'), self.data_dict.get('novel_author')))
        flag2 = []
        chapter_list = data_dict.get('chapter_dict_list')
        for i, data in enumerate(chapter_list):
            for key, value in data.items():
                chapter_title, chapter_content = key, value
            chapter_dict = {
                'novel_id': novel_id,
                'chapter_number': i,
                'chapter_title': chapter_title,
                'chapter_content': chapter_content
            }
            # 调用小说各章节数据存入数据库的方法
            if self.sql_novelChapter_commit(self.pool, chapter_dict):
                pass
            else:
                flag2.append(i)  # 记录未成功保存的章节
        result_dict = {
            'flag1': flag1,
            'flag2': flag2
        }
        self.finished.emit(result_dict)  # 发送存储结束的字典信号

    @staticmethod
    def sql_novelBase_commit(pool: PooledDB, data_dict: dict) -> bool:
        """
        用于实现从连接池获取连接，进行提交小说基本信息数据的方法
        执行逻辑为：小说名存在即更新，不存在即创建
        数据字典的格式需为：
        novel_title  小说名
        novel_author  作者
        novel_summary 小说总结
        novel_status  小说状态
        :param pool: 线程池
        :param data_dict: 小说基本信息 dict
        :return:True/False
        """
        connection = pool.connection()
        flag = True
        now_time = str(datetime.datetime.now())[0:19]  # 获取当前操作时间
        try:
            # 从数据字典中获得数据
            novel_title = data_dict['novel_title']  # 小说标题
            novel_author = data_dict['novel_author']  # 小说作者
            novel_summary = data_dict['novel_summary']  # 小说总结
            novel_status = data_dict['novel_status']  # 小说状态：已完结/连载中
            # 查看数据库中是否存在该小说
            cursor = connection.cursor()
            cursor.execute(f"SELECT novel_id FROM project1_1 WHERE title='{novel_title}';")
            # 将小说基本信息插入数据库
            if cursor.fetchall():  # 判断数据库中是否已存在了同名数据
                cursor.execute(f"update project1_1 set update_at='{now_time}' where title='{novel_title}';")  # 跟新该小说在数据库中的跟新时间
                # 日志更新数据生成
                log_data = {
                    'log_data': '小说： {} 更新'.format(novel_title),
                    'log_level': '1'
                }
            else:
                cursor.execute(f"insert into project1_1(title, author, summary, status, created_at, update_at) VALUES ('{novel_title}', '{novel_author}', '{novel_summary}', '{novel_status}', '{now_time}', '{None}');")  # 更新小说
                # 日志更新数据生成
                log_data = {
                    'log_data': '小说：{} 创建'.format(novel_title),
                    'log_level': '1'
                }
            connection.commit()  # 提交数据
            # 调用日志数据更新的方法
            Mysql_program.log_update(pool, log_data)
        except Exception as e:
            # 发生异常时回滚事务
            connection.rollback()
            # 调用日志更新
            log_dict = {
                'log_data': '数据更新失败！' + str(e),
                'log_level': '2'
            }
            Mysql_program.log_update(pool, log_dict)
            flag = False
        finally:
            # 注意：这里不需要显式关闭cursor和connection
            # 因为连接池会自动管理连接的开启和关闭
            return flag

    @staticmethod
    def sql_novelChapter_commit(pool: PooledDB, data_dict: dict) -> bool:
        """
        用于实现从连接池获取连接，进行提交小说章节信息数据的方法
        执行逻辑为：小说名存在即更新，不存在即创建
        数据字典的格式需为：
        novel_id  小说ID
        chapter_number 章节号
        chapter_title 小说章节名
        chapter_content  小说章节内容
        :param pool: 数据库连接线程池
        :param data_dict: 小说章节内容 dict
        :return:True/False
        """
        connection = pool.connection()  # 获得连接
        cursor = connection.cursor()
        flag = True
        now_time = str(datetime.datetime.now())[0:19]  # 获取当前操作时间
        try:
            # 从数据字典中获得数据
            novel_id = data_dict.get('novel_id')  # 小说ID
            chapter_number = data_dict.get('chapter_number')  # 章节号
            chapter_title = data_dict.get('chapter_title')  # 章节标题
            chapter_content = data_dict.get('chapter_content')  # 章节内容
            # 查看数据库中是否存在该章节
            cursor.execute(f"select chapter_id  from project1_2 where chapter_number='{int(chapter_number)}' and novel_id='{novel_id}';")
            # 将章节信息更新至数据库
            if cursor.fetchall():  # 判断数据库中是否已存在了同名数据
                cursor.execute(f"update project1_2 set update_at='{now_time}' where title='{chapter_title}';")  # 跟新该章节在数据库中的跟新时间
                cursor.execute(f"update project1_2 set content='{chapter_content}' where chapter_number='{int(chapter_number)}' and chapter_title ='{chapter_title}';")
                # 日志更新数据生成
                log_data = {
                    'log_data': '小说；{} 章节： {} 更新'.format(novel_id, chapter_title),
                    'log_level': '1'
                }
            else:
                cursor.execute(f"insert into project1_2(novel_id, chapter_number, title, content, created_at, update_at) VALUES ('{int(novel_id)}', '{int(chapter_number)}', '{chapter_title}', '{chapter_content}', '{now_time}', '{None}');")  # 更新小说
                # 日志更新数据生成
                log_data = {
                    'log_data': '小说：{} 章节： {} 创建'.format(novel_id, chapter_title),
                    'log_level': '1'
                }
            connection.commit()  # 提交数据
            # 调用日志数据更新的方法
            Mysql_program.log_update(pool, log_data)
        except Exception as e:
            # 发生异常时回滚事务
            connection.rollback()
            # 调用日志更新
            log_dict = {
                'log_data': '数据更新失败！' + str(e),
                'log_level': '2'
            }
            Mysql_program.log_update(pool, log_dict)
            flag = False
        finally:
            # 注意：这里不需要显式关闭cursor和connection
            # 因为连接池会自动管理连接的开启和关闭
            return flag

    @staticmethod
    def log_update(pool: PooledDB, log_dict: dict) -> None:
        """
        用于实现日志更新的方法
        :param pool: 数据库连接的线程池
        :param log_dict: 日志字典
        :return: None
        """
        connection = pool.connection()  # 从连接池中获取连接
        now_time = str(datetime.datetime.now())[0:19]  # 获取当前操作时间部分
        try:
            # 获得数据
            log_data = log_dict.get("log_data")  # 日志数据
            log_level = log_dict.get("log_level")  # 日志等级
            # 获取数据库连接
            cursor = connection.cursor()
            cursor.execute(f"insert into project1_3(log_data, log_level, log_time) VALUE ('{log_data}', '{Mysql_program.LOG_LEVEL.get(log_level)}', '{now_time}');")
            connection.commit()
        except Exception as e:  # 若此处抛出异常，则说明错误比较严重了
            cursor = connection.cursor()
            cursor.execute("insert into project1_3(log_data, log_level, log_time) VALUE ('{}', '{}', '{}');".format("日志更新失败！:" + str(e), Mysql_program.LOG_LEVEL.get("4"), now_time))
            connection.commit()
        finally:
            pass


p_lock = Lock()  # 多进程锁


if __name__ == "__main__":
    app = QApplication(sys.argv)

    main_form = QMainWindow()

    main = Main_program(main_form)

    main_form.show()

    sys.exit(app.exec_())
